package ccpv;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map.Entry;

import multiarray.MultiArray;

/**
 * Contains settings that will be used during the runtime. For example this will contain information on how trends and parameters will change over time.
 * This will also encapsulate the hierarchical labels used for output.
 * @author Robert Puckett
 *
 */
public class RunTimeSettings {
	private double lambda = 0.5;
	private double phi = 0.2; // not used anymore
	private double I = 0.5;
	private double c = 0.2;
	private double f = 0.1; // equivalent of 1.1
	private double precision = 1E-20;
	
	private int[] years;
	public MultiArray<String> group_names;
	public HashMap<String,int[]> group_names2 = new HashMap<String,int[]>();
	
	public ArrayList<NamedArrayList<String>> ascriptive_names = new ArrayList<NamedArrayList<String>>();
	
	HashMap<Integer,MultiArray<MultiArray<Double>>> altruism = new HashMap<Integer,MultiArray<MultiArray<Double>>>();
	HashMap<Integer,MultiArray<Data<Double>>> data = new HashMap<Integer,MultiArray<Data<Double>>>();

   	

	public RunTimeSettings() {
		
	}

	public ArrayList<int[]> getGroupCharacters() {
		ArrayList<int[]> out = new ArrayList<int[]>();
		for(Entry<String,int[]> st: group_names2.entrySet()) {
			out.add(st.getValue());
		}
		return out;
	}
	
	public int[] getYears() {
		int[] yearscopy = new int[years.length];
		for (int i = 0; i< years.length; i++) {
			yearscopy[i] = years[i];
		}
		return yearscopy;
	}
	
	public void Initalize(ArrayList<Integer> m_years, HashMap<String,int[]> m_group_names, ArrayList<NamedArrayList<String>> ascriptiveNames, ArrayList<GroupRecord> group_records) {
		int[] ascrDims = new int[ascriptiveNames.size()];
		System.out.print("Ascriptive Dimensions: ");
		for(int i = 0; i<ascriptiveNames.size();i++) {
			ascrDims[i] = ascriptiveNames.get(i).size();
			System.out.print(ascrDims[i] + " ");
		}
		System.out.println();
		
		
		group_names = new MultiArray<String>(ascrDims);
		for(Entry<String,int[]> et: m_group_names.entrySet()) {
			group_names.setElementAtIndex(et.getValue(), et.getKey());
			group_names2.put(et.getKey(), et.getValue());
		}
		
		for (int i = 0; i<ascriptiveNames.size(); i++) {
			this.ascriptive_names.add(new NamedArrayList<String>(ascriptiveNames.get(i).name));
			for (int j=0; j<ascriptiveNames.get(i).size(); j++) {
				this.ascriptive_names.get(i).add(ascriptiveNames.get(i).get(j));
			}
		}
		
		
		ArrayList<Integer> dates = new ArrayList<Integer>();
		for (int i=0; i<m_years.size(); i++) {
			data.put(m_years.get(i), new MultiArray<Data<Double>>(ascrDims));
			dates.add(m_years.get(i));
		}
		Collections.sort(dates);
		this.years = new int[dates.size()];
		for (int i =0; i<dates.size(); i++) {
			this.years[i] = dates.get(i);
		}
		
		for (int i=0; i<m_years.size(); i++) {
			this.altruism.put(m_years.get(i), new MultiArray<MultiArray<Double>>(ascrDims));
		}
		
		int max = 1;
		for(int i = 0; i<ascrDims.length; i++) {
			max = max*ascrDims[i];
		}

		boolean carry = false;
		int[] position = new int[ascrDims.length];
        int i;
        
		for (int j = 0; j<max; j++) {
			for (int k=0; k<m_years.size(); k++) {
				this.altruism.get(m_years.get(k)).setElementAtIndex(position, new MultiArray<Double>(ascrDims));
			}
		 i = 0;
		 do {
			carry = false;
			if (((position[i]+1)%ascrDims[i])==0) {
				carry = true;
			}
			position[i] = (position[i]+1)%ascrDims[i];
			i++;
		 } while ((carry == true)&&(i < position.length));
		
		}
		
		for(GroupRecord rec: group_records) {
			System.out.print(rec.year + ": ");
			printIntArray(rec.ascriptiveChar);
			this.data.get(rec.year).setElementAtIndex(rec.ascriptiveChar, new Data<Double>(rec.population,rec.values));
		}
	}
	
	private void printIntArray(int[] array) {
		for (int i = 0; i<array.length; i++) {
			System.out.print(array[i] + " ");
		}
		System.out.println();
	}
	
	public void setAltruism(int year, int[] group1, int[] group2, double value) {
		System.out.println(year);
		System.out.print("Group 1 : ");printIntArray(group1);
		System.out.print("Group 2 : ");printIntArray(group2);
		if (this.altruism.get(year) == null) {
			iterateInitializeAltruismLevel(year);
		}
		
		this.altruism.get(year).getElementAtIndex(group1).setElementAtIndex(group2, value);		
	}

	public void setAltruism(int year, String group1, String group2, double value) {
		int[] grp1 = this.group_names2.get(group1);
		int[] grp2 = this.group_names2.get(group2);
		this.altruism.get(year).getElementAtIndex(grp1).setElementAtIndex(grp2, value);
	}

	public int[] getGroupCharacter(String name) {
		return group_names2.get(name);
	}
	
	public String getGroupName(int[] character) {
		return group_names.getElementAtIndex(character);
	}
	
	public String getAscriptiveName(int category, int index) {
		return ascriptive_names.get(category).get(index);
	}
	
	public String[] getAscriptiveCategories() {
		String[] result = new String[ascriptive_names.size()];
		for (int i = 0; i<ascriptive_names.size(); i++) {
			result[i] = ascriptive_names.get(i).name;
		}
		return result;
	}
	
	public String[] getAscriptiveNames(int[] character) {
		assert(character.length == ascriptive_names.size());
		String[] result = new String[ascriptive_names.size()];
		for (int i = 0; i<ascriptive_names.size(); i++) {
			result[i] = ascriptive_names.get(i).get(character[i]);
		}
		return result;
	}
	
	
	private double interpolate(int year, int year1, double value1, int year2, double value2) {
		// x's are years, y's are values
		// y - y1 = m(x-x1)
		// m = (y2 - y1) / (x2 - x1)
		// y =  mx + b
		// y = m(x-x1) + y1
		// b = -mx1 + y1
		double m = (value2-value1)/(year2-year1);
		double b = -1*m*year1 + value1;
		return m*year + b;
	}
	
	private boolean isValidAscriptiveCharacter(int[] character) {
		if (character.length != ascriptive_names.size()) {
			return false;
		}

		for (int i=0; i<ascriptive_names.size(); i++){
			if (character[i] >= ascriptive_names.get(i).size()) {
				return false;
			}
		}
		for (int i=0; i<character.length; i++){
			  if (character[i] < 0) { return false; }
		}
		return true;
	}
	
	/**
	 * @param year
	 * @param character
	 * @return
	 */
	public int getPopulation(int year, int[] character) {
		if (!isValidAscriptiveCharacter(character)) return 0;
		
		if (years.length == 1) {
			// Returns 0 if the year is before the only data point, otherwise, returns the data point we have
			if (year < years[0]) return 0;
			else {
				return data.get(years[0]).getElementAtIndex(character).getPopulation();
			}
		} else {
			// find pair of closest years
			if (data.containsKey(year)) {
				return data.get(year).getElementAtIndex(character).getPopulation();
			} else {
				if (year < years[0]) {
					// extrapolate off the low end
					int value = (int)interpolate(year, years[0], data.get(years[0]).getElementAtIndex(character).getPopulation(), years[1], data.get(years[1]).getElementAtIndex(character).getPopulation());
					if (value < 0) value = 0;
					return value;
				} else if (year > years[years.length-1]) {
					// extrapolate off the high end
					int value = (int)interpolate(year, years[years.length-2], data.get(years[years.length-2]).getElementAtIndex(character).getPopulation(), years[years.length-1], data.get(years[years.length-1]).getElementAtIndex(character).getPopulation());
					if (value < 0) value = 0;
					return value;
				} else {
					// interpolate somewhere in the middle
					int minY = years[0];
					int maxY = years[years.length-1];
					for (int i = 0; i<years.length; i++) {
						if (years[i]<year) minY = years[i];
						else break;
					}
					for (int i = years.length-1; i>=0; i--) {
						if (years[i]>year) maxY = years[i];
						else break;
					}
					int value = (int)interpolate(year, minY, data.get(minY).getElementAtIndex(character).getPopulation(), maxY, data.get(maxY).getElementAtIndex(character).getPopulation());
					if (value < 0) value = 0;
					return value;
				}
				
				
			}
		}
		
		
	}
	
	
	private void iterateInitializeAltruismLevel(int year) {
		if (altruism.containsKey(year)) return;
		
		int[] ascrDims = this.getAscriptiveLengths();
		altruism.put(year, new MultiArray<MultiArray<Double>>(ascrDims));
		
		int max = 1;
		for(int i = 0; i<ascrDims.length; i++) {
			max = max*ascrDims[i];
		}

		boolean carry = false;
		int[] position = new int[ascrDims.length];
        int i;
        
		for (int j = 0; j<max; j++) {
  		 this.altruism.get(year).setElementAtIndex(position, new MultiArray<Double>(ascrDims));
		 i = 0;
		 do {
			carry = false;
			if (((position[i]+1)%ascrDims[i])==0) {
				carry = true;
			}
			position[i] = (position[i]+1)%ascrDims[i];
			i++;
		 } while ((carry == true)&&(i < position.length));
		
		}
		
	}
	
	public double getValue(int year, int[] character, int index) {
		if (years.length == 1) {
			return data.get(year).getElementAtIndex(character).getValueAt(index);
		} else {
			// find pair of closest years
			if (data.containsKey(year)) {
				return data.get(year).getElementAtIndex(character).getValueAt(index);
			} else {
				if (year < years[0]) {
					// extrapolate off the low end
					int value = (int)interpolate(year, years[0], data.get(years[0]).getElementAtIndex(character).getValueAt(index), years[1], data.get(years[1]).getElementAtIndex(character).getValueAt(index));
					if (value < 0) value = 0;
					return value;
				} else if (year > years[years.length-1]) {
					// extrapolate off the high end
					int value = (int)interpolate(year, years[years.length-2], data.get(years[years.length-2]).getElementAtIndex(character).getValueAt(index), years[years.length-1], data.get(years[years.length-1]).getElementAtIndex(character).getValueAt(index));
					if (value < 0) value = 0;
					return value;
				} else {
					// extrapolate somewhere in the middle
					int minY = years[0];
					int maxY = years[years.length-1];
					for (int i = 0; i<years.length; i++) {
						if (years[i]<year) minY = years[i];
						else break;
					}
					for (int i = years.length-1; i>=0; i--) {
						if (years[i]>year) maxY = years[i];
						else break;
					}
					int value = (int)interpolate(year, minY, data.get(minY).getElementAtIndex(character).getValueAt(index), maxY, data.get(maxY).getElementAtIndex(character).getValueAt(index));
					if (value < 0) value = 0;
					return value;
				}
			}
		}
	}
	
	
	public double getAltruism(int[] source, int[] target, int date) {
		if (altruism.get(date) == null) return 0;
		if (altruism.get(date).getElementAtIndex(source) == null) return 0;
		if (altruism.get(date).getElementAtIndex(source).getElementAtIndex(target) == null) return 0;
		// take that null pointers exceptions!
		
		return altruism.get(date).getElementAtIndex(source).getElementAtIndex(target);
	}

	
	public int[] getAscriptiveLengths() {
		int[] results = new int[ascriptive_names.size()];
		for(int i= 0; i<ascriptive_names.size(); i++ ) {
			results[i] = ascriptive_names.get(i).size();
		}
		return results;
	}


	
	public int getAscriptiveIndex(int index, String st) {
		String val = st.trim();
		for (int i = 0; i<ascriptive_names.get(index).size(); i++) {
		  if (ascriptive_names.get(index).get(i).equals(val)) {
			  return i;
		  }
		}
		return -1; // not found
	}
	
	public int getAscriptiveIndex(String st) {
		String val = st.trim();
		for (int i = 0; i<ascriptive_names.size(); i++) {
		  if (ascriptive_names.get(i).name == val) {
			  return i;
		  }
		}
		return -1; // not found
	}
	
	
	public String toString() {
		return "";
	}
	
	public String toCsvFormat() {
		return "";
	}

	/**
	 * @param lambda the lambda to set
	 */
	public void setLambda(double lambda) {
		this.lambda = lambda;
	}

	/**
	 * @return the lambda
	 */
	public double getLambda() {
		return lambda;
	}

	/**
	 * @param phi the phi to set
	 */
	public void setPhi(double phi) {
		this.phi = phi;
	}

	/**
	 * @return the phi
	 */
	public double getPhi() {
		return phi;
	}

	/**
	 * @param precision the precision to set
	 */
	public void setPrecision(double precision) {
		this.precision = precision;
	}

	/**
	 * @return the precision
	 */
	public double getPrecision() {
		return precision;
	}

	/**
	 * @param i the i to set
	 */
	public void setI(double i) {
		I = i;
	}

	/**
	 * @return the i
	 */
	public double getI() {
		return I;
	}

	/**
	 * @param c the c to set
	 */
	public void setC(double c) {
		this.c = c;
	}

	/**
	 * @return the c
	 */
	public double getC() {
		return c;
	}

	/**
	 * @param f the f to set
	 */
	public void setF(double f) {
		this.f = f;
	}

	/**
	 * @return the f
	 */
	public double getF() {
		return f;
	}


}